import asyncio
from contextlib import contextmanager

from pocketbase import PocketBase  # Client also works the same
from pocketbase.models import FileUpload
from pocketbase.services import AdminAuthResponse
from pocketbase.services.record_service import RecordAuthResponse

from application.utils import get_rich_logger, METASTORE_URL
from application.handle_exception import pb_authentication_exception, pb_operation_exception
from application.aiohttp_utils import post_request_with_body, get_request_util, patch_request_with_path_and_body, \
    delete_request_with_path
from domain.authentication import TokenData, Admin, UserInDB, User

logger = get_rich_logger()


@contextmanager
def connect_with_pocketbase():
    # start-up a Pocketbase session
    client: PocketBase = PocketBase(METASTORE_URL)
    try:
        yield client
    finally:
        # complete, close session
        client.auth_store.clear()


async def rest_authentication(
        api: str,
        token: TokenData
):
    logger.debug(f"Requesting authentication api: '{api}'.")
    logger.debug(f"With token data: {token}")
    target_url = METASTORE_URL + api
    post_task = asyncio.create_task(
        post_request_with_body(
            request_url=target_url,
            request_body={
                "identity": token.email,
                "password": token.password
            }
        )
    )
    ret_auth = await post_task
    logger.debug(f"Request task compelte. Return value: {ret_auth}")
    return ret_auth


def user_auth_via_client(client: PocketBase, user: User):
    record_auth_response: RecordAuthResponse = client \
        .collection("users") \
        .auth_with_password(user.email, user.password)
    if not record_auth_response.token or not record_auth_response.record:
        raise pb_authentication_exception
    return record_auth_response


def admin_auth_via_client(client: PocketBase, admin: Admin):
    admin_auth_response: AdminAuthResponse = client \
        .admins \
        .auth_with_password(admin.email, admin.password)
    if not admin_auth_response.token or not admin_auth_response.admin:
        raise pb_authentication_exception
    return admin_auth_response


async def pb_list_from(collection: str, filter_predicate: str):
    target_url = f"{METASTORE_URL}/api/collections/{collection}/records"
    ret = await get_request_util(
        request_url=target_url,
        request_query={"filter": filter_predicate}
    )
    return ret


async def client_list_from(
        client: PocketBase,
        collection: str,
        filter_predicate: str,
        sort: str = "-created,id"  # DESC by created and ASC by id
):
    result = client.collection(collection).get_list(
        query_params={
            "sort": sort,
            "filter": filter_predicate
        }
    )
    return result


async def pb_full_list(
        collection: str,
        query: dict | None = None):
    if not query:
        # DESC by created and ASC by id
        query = {"sort": "-created,id"}
    target_url = f"{METASTORE_URL}/api/collections/{collection}/records"
    ret = await get_request_util(
        request_url=target_url,
        request_query=query
    )
    return ret


async def client_fetch_full_list_from(
        client: PocketBase,
        collection: str,
):
    result = client.collection(collection).get_full_list()
    return result


async def client_search_first_item(
        client: PocketBase,
        collection: str,
        filter_predicate: str,
        sort: str = "-created,id"  # DESC by created and ASC by id
):
    result = client.collection(collection).get_list(
        page=1,
        per_page=1,
        query_params={
            "sort": sort,
            "filter": filter_predicate
        }
    )
    return result


async def pb_fetch_from(collection: str, record_id: str):
    target_url = f"{METASTORE_URL}/api/collections/{collection}/records/{record_id}"
    ret = await get_request_util(target_url)
    return ret


async def client_fetch_from(
        client: PocketBase,
        collection: str,
        target_id: str
):
    result = client.collection(collection).get_one(
        id=target_id
    )
    return result


async def client_create_new(
        client: PocketBase,
        collection: str,
        data: dict
):
    result = client.collection(collection).create(
        body_params=data
    )
    return result


async def pb_create_new(collection: str, data: dict):
    target_url = f"{METASTORE_URL}/api/collections/{collection}/records"
    ret = await post_request_with_body(
        request_url=target_url,
        request_body=data
    )
    return ret


async def pb_update_to(
        collection: str,
        record_id: str,
        data: dict):
    target_url = f"{METASTORE_URL}/api/collections/{collection}/records/{record_id}"
    ret = await patch_request_with_path_and_body(
        request_url=target_url,
        request_body=data
    )
    return ret


async def client_update_to(
        client: PocketBase,
        collection: str,
        target_id: str,
        data: dict
):
    ret = client.collection(collection).update(
        id=target_id,
        body_params=data
    )
    return ret


async def pb_delete_from(
        collection: str,
        record_id: str
):
    target_url = f"{METASTORE_URL}/api/collections/{collection}/records/{record_id}"
    ret = await delete_request_with_path(
        request_url=target_url
    )
    return ret


async def client_delete_record(
        client: PocketBase,
        collection: str,
        target_id: str
):
    result = client.collection(collection).delete(
        id=target_id
    )
    return result


# list and filter "example" collection records
async def get_records_with_filter(
        user: UserInDB,
        collection: str,
        filter_predicate: str
):
    with connect_with_pocketbase() as pb_client:
        user_auth_response = user_auth_via_client(
            client=pb_client,
            user=user
        )
        logger.debug("Metastore admin authentication done. "
                     f"Response: {user_auth_response.record, user_auth_response.token}")
        result: dict = await pb_list_from(
            collection=collection,
            filter_predicate=filter_predicate
        )
        logger.debug(f"Filtered complete.")
        if not result:
            raise pb_operation_exception
    return result


async def update_record_with_image_file(
        user_in_db: UserInDB,
        collection: str,
        record_id: str,
        image_path: str,
        field: str = "avatar",
):
    with connect_with_pocketbase() as pb_client:
        user_auth_response = user_auth_via_client(
            client=pb_client,
            user=user_in_db
        )
        logger.debug("Metastore admin authentication done. "
                     f"Response: {user_auth_response.record, user_auth_response.token}")
        # create record and upload file to image field
        former_record = await pb_fetch_from(
            collection=collection,
            record_id=record_id
        )
        if former_record['status'] != 200:
            raise pb_operation_exception
        new_dict = former_record['response']
        with open(image_path, 'rb') as file_obj:
            new_dict[field] = FileUpload((image_path.split("/")[-1], file_obj))
        update_ret: dict = await pb_update_to(
            collection=collection,
            record_id=record_id,
            data=new_dict
        )
    return update_ret
